#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         BlogPost.py
#
# Copyright:    Copyright (c) 2004, Faulkner Technologies
#
# Author:       Darcy Clark
#
# Description:  This file defines the BlogPost class, a simple
#               action to post to blogs supporting the Blogger API
#
# See Also:     Action, Event
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.Action import Action, ActionInfo
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.KERFProcessor import *

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from ImageFile import ImageFile
from Products.CMFCore.utils import getToolByName

#####################################################################
## Python Library Imports
try:
    import blogger
    BLOGGER=True
except ImportError:
    BLOGGER=False
    
    
    #raise ImportError, 'Please install the PyBlogger library from http://beetle.cbtlsl.com/archives/category/pyblogger/'
import httplib
import sha
import base64
import binascii
import time

#####################################################################
## BlogPostInfo Class Definition
class BlogPostInfo(ActionInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """
    
    #---------------------------------------------------------------#
    
    def getClassName(self):
        """
            Returns the name of the class for which 
            we are providing information.
        """
        return "BlogPost"

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Blog Post"
        
    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            Return the identity type.  
        """
        
        return "blog"
        
    #---------------------------------------------------------------#
    
    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """
        
        return ImageFile('skins/BlogPost_icon.gif', globals())
   
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        ActionInfo.onConfigureProperties(self)

        #add the 'URL' property        
        self.addPropertyDefinition(PropertyDefinition(identity="URL", displayName="Blog URL", description="URL of remote blog site", defaultValue="", propertyType=pt_STRING, propertyFormat=pf_TEXT_SINGLELINE, propertyStructure=ps_ATOMIC, isMandatory=true))        

        #add the 'blogType' property        
        self.addPropertyDefinition(PropertyDefinition(identity="blogType", displayName="Blog Type", description="What type of blog are you posting to?", propertyType=pt_STRING, propertyFormat=pf_NATIVE, hasEnumeratedValues=true, enumeratedValuesList=['Quills (BloggerAPI)','Blogger (AtomAPI)'], defaultValue="Quills (BloggerAPI)", isMandatory=true))     

        #add the 'blogID' property        
        self.addPropertyDefinition(PropertyDefinition(identity="blogID", displayName="Blog ID", description="Your weblog ID (optional, but helpful if you have it)", defaultValue="", propertyType=pt_STRING, propertyFormat=pf_TEXT_SINGLELINE, propertyStructure=ps_ATOMIC))        

        #add the 'username' property        
        self.addPropertyDefinition(PropertyDefinition(identity="username", displayName="Username", description="Your weblog username", defaultValue="", propertyType=pt_STRING, propertyFormat=pf_TEXT_SINGLELINE, propertyStructure=ps_ATOMIC, isMandatory=true))        

        #add the 'password' property        
        self.addPropertyDefinition(PropertyDefinition(identity="password", displayName="Password", description="Your weblog password", defaultValue="", propertyType=pt_STRING, propertyFormat=pf_TEXT_SINGLELINE, propertyStructure=ps_ATOMIC, isMandatory=true))        

        #add the 'title' property        
        self.addPropertyDefinition(PropertyDefinition(identity="postTitle", displayName="Title", description="The title of your blog post (KERF Friendly)", defaultValue="", propertyType=pt_STRING, propertyFormat=pf_TEXT_MULTILINE, propertyStructure=ps_ATOMIC))        

        #add the 'text' property        
        self.addPropertyDefinition(PropertyDefinition(identity="text", displayName="Text", description="The text to post (KERF Friendly)", defaultValue="The actual text you'd like to post", propertyType=pt_STRING, propertyFormat=pf_TEXT_MULTILINE, propertyStructure=ps_ATOMIC))

        #add the 'publish' property        
        self.addPropertyDefinition(PropertyDefinition(identity="publish", displayName="Publish", description="Publish after post?", defaultValue=true, propertyType=pt_BOOLEAN, propertyFormat=pf_CHECKBOX, propertyStructure=ps_ATOMIC))        

    #---------------------------------------------------------------#

#####################################################################
## BlogPost Action Class Definition
class BlogPost(Action):
    """
        A class to represent a simple folder that may contain Elements
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = BlogPostInfo()
    
    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the Folder"        

        Action.__init__(self, id, identity)
                
        #perform onAfterInstanciation processing
        self.onAfterInstanciation()

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object 
            has been loaded into memory.  
            
            This is useful for performing backwards compatibility
        """        
        
        #perform default processing
        Action.onAfterInstanciation(self)

    #---------------------------------------------------------------#

    def onAfterIdentityChange(self, identityMappings):
        """
            Handle Knowledge Engine specific actions after the 
            identity of this object has changed.
            
            The identityMappings dictionary holds 
            mappings from oldIdentities -> newIdentities
            (including this object)
                        
        """

        #perform default processing
        Action.onAfterIdentityChange(self, identityMappings)
        
        #update the KERF friendly properties
        self.text = updateKERF(self.text, identityMappings)
        
    #---------------------------------------------------------------#
    
    def execute(self, assessment, user = None, previousActionSuccess = true):
        """
           Send post to blog server
        """    
        DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"

        #### construct the post
        
        # Quills expects the title and body
        # of a post to be combined, separated by '\n' as
        # Blogger API doesn't support titles
        # add datetime to differentiate titles - Quills barfs on posts with the same title
        title = str(resolveKERF(self.postTitle, assessment))
        if not title:
            title = "posted from KE - set a title in your BlogPost action to replace this text"
        new_title = str(self.ZopeTime().ISO()) + ' ' + title + '\n'
        
        # construct the body - we'll prepend a bunch of boilerplate before the main body of post
        model = str(assessment.getModel().getTitleOrId())
        name = str(assessment.getName())
        instantCreated = str(assessment.getInstantCreated().strftime(DATETIME_FORMAT))
        instantModified = str(assessment.getInstantModified().strftime(DATETIME_FORMAT))
        if assessment.getIsLocked():
            status = "Locked"
        else:
            status = "Unlocked"
        user = str(assessment.getCreatedBy())

        # get score from scored report
        modelObj = assessment.getModel()
        up = getToolByName(self, 'knowledgeengine_userproviderservice')
        userObj = up.getUser(user)
        reports = modelObj.getReports(userObj, 'SingleAssessmentReport')
        if reports:
            scored_reports = [r for r in reports if r.getClassInfo().getIdentityType() == 'scoredreport']
            raw_score = scored_reports[0].getScore(assessment)
            score = str(raw_score)
            raw_total = scored_reports[0].getMaximumPossibleScore()
            total = str(raw_total)
            percentage = '%.2f' % (raw_score / raw_total * 100.0)
        else:
            total = percentage = 'N/A'
            score = 'N/A (need to add a scored report to model to get score)'
        
        # assemble blog post
        body = model + ' - ' + name + '\n'
        body = body + "Created on " + instantCreated + '\n'
        body = body + "Last Modified on " + instantModified + '\n'
        body = body + "Current Status: " + status + '\n'
        body = body + "User: " + user + '\n'
        body = body + "Score: " + score + ' out of a possible ' + total + ' (' + percentage + '%)''\n\n'
        body = body + str(resolveKERF(self.text, assessment))
        
        post = new_title + body
                
        # get various posting variables
        publish = self.getPropertyValue("publish")
        blogType = self.getPropertyValue("blogType")
        blogID = self.getPropertyValue("blogID")
        username = self.getPropertyValue("username")
        password = self.getPropertyValue("password")
        url = self.getPropertyValue("URL")
        
        #### post to blog
        if blogType == 'Quills (BloggerAPI)':
            # use Blogger API, because that is all Quills supports
            if not blogID: # try to get BlogID from Quills server if not provided
                userBlogsList = blogger.getUsersBlogs(username, password, url)
                blogID = userBlogsList[0]['blogid']
            postID = self.postQuills(blogID, username, password, post, publish, url)
        else:
            # use ATOM API
            postID = self.postAtom(blogID, blogType, username, password, title, body)

        # return that the action did something        
        return true        
        
    #---------------------------------------------------------------#
    
    def postQuills(self, blogID, username, password, post, publish, url):
        """
            send a new post to a Quills blog via BloggerAPI
        """

        postID = blogger.newPost(blogID, username, password, post, publish, url)
        return postID
        
    #---------------------------------------------------------------#
    
    def postAtom(self, blogID, blogType, username, password, title, body):
        """
            send a new post to a Blogger blog via ATOM API
            using ATOM API because Blogger API has problems - can't set title etc..
        """
        host = "www.blogger.com"
        if not blogID:
            userBlogsList = blogger.getUsersBlogs(username, password)  # use BloggerAPI to get blogID
            blogID = userBlogsList[0]['blogid']

        path = "/atom/%s" % (blogID)
        created = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())

        #base64string = base64.encodestring('%s:%s' % (username, password))[:-1]
        #auth = "Basic " + base64string
        #headers = {"Content-type": "application/atom+xml", "Authorization": auth }

        cookie = binascii.b2a_base64("%s:%s" % (username, password))
        headers = {
            "Content-type": 'application/atom+xml',
            "Authorization": 'BASIC %s' % cookie.strip(),
            "UserAgent": 'pyatomblogger',
            } 
    
        body = """<?xml version="1.0" encoding="UTF-8" ?>
        <entry xmlns="http://purl.org/atom/ns#">
        <generator url="http://www.faulknertechnologies.com/">Faulkner Technologies Knowledge Engine</generator>
        <title mode="escaped" type="text/html">""" + title + """</title>
        <issued>""" + created + """</issued>
        <content mode="escaped" type="text/html">""" + body + """</content>
        </entry>"""

        conn = httplib.HTTPSConnection(host)
        conn.set_debuglevel(1)
        conn.request("POST", path, body, headers)
        response = conn.getresponse()
        conn.close()

        return response.read()

    #---------------------------------------------------------------#

            
#####################################################################
## Class Initialisation

# Register Class Information with the Knowledge Engine
if BLOGGER:
    registerGenericObjectClass(BlogPost)

#####################################################################